################################################################################
#  Copyright (c) by Medtronic, Inc., 2001.  
#  $HeadURL: http://build-tool.googlecode.com/svn/trunk/BuildTool/quals/main_fw/tools/BuildTools/galaxy_hexcrc.pl $
#  $Revision: 71 $
#  $Date: 2013-08-12 13:43:22 +0000 (Mon, 12 Aug 2013) $
################################################################################

use strict;

my $purpose;
my $synopsis;
my $description;

$purpose      = "##########################################################################################\n";
$purpose     .= "#                                                                                         \n";
$purpose     .= "#  PURPOSE                                                                                \n";
$purpose     .= "#                                                                                         \n";
$purpose     .= "#    This script calculates a CCITT-CRC value from an Intel-32 hex file, and stores the   \n";
$purpose     .= "#    CRC a specified address in the output hex file.                                      \n";
$purpose     .= "#                                                                                         \n";

$synopsis     = "#                                                                                         \n";
$synopsis    .= "#  SYNOPSIS                                                                               \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#    Usage:  hexcrc -a <start_addr> -c <crc_addr> -s <code_size> -i <filename> -o <filename> -l <filename>\n";
$synopsis    .= "#               - OR -                                                                    \n";
$synopsis    .= "#            hexcrc -h                                                                    \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#    Arguments (note that all arguments are optional):                                    \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -a <start_addr>:     The starting address for the FW image. This  defaults   \n";
$synopsis    .= "#                                 to 0x10000000, which is appropriate for flash.          \n";
$synopsis    .= "#                                 For ROM, the argument value should be 0x01000000.       \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -b <build rev>:      The build revision. This will be loaded into the image. \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -c <crc_addr>:       The address at which the address of the CRC should be   \n";
$synopsis    .= "#                                 written (and yes, the indirection is intentional).      \n";
$synopsis    .= "#                                 This defaults to 0x10000448, which is after the vector  \n";
$synopsis    .= "#                                 table and build info at the low end of flash.           \n";
$synopsis    .= "#                                 For ROM, the argument value should be 0x01000448.       \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -h:                  Displays help text for the script.                      \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -i <filename>:       The name of the input intel.hex format file. Default is \n";
$synopsis    .= "#                                 code.intel.hex.                                         \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -l <filename>:       The name of the log file. If this argument is omitted,  \n";
$synopsis    .= "#                                 no log file will be generated.                          \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -o <filename>:       The name of the output file. Default is                 \n";
$synopsis    .= "#                                 code_crc.intel.hex.                                     \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -s <code_size>:      The size (number of bytes), in hex, of the code. This   \n";
$synopsis    .= "#                                 value is compared to the observed code size (as         \n";
$synopsis    .= "#                                 determined by the min/max addresses encountered in the  \n";
$synopsis    .= "#                                 input file) as a validity check. If this argument is    \n";
$synopsis    .= "#                                 omitted, no size check is performed.                    \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#            -v:                  Enables verbose output. This will output the log info   \n";
$synopsis    .= "#                                 (the same info that is written to the log file) on the  \n";
$synopsis    .= "#                                 screen.                                                 \n";
$synopsis    .= "#                                                                                         \n";
$synopsis    .= "#    Example: galaxy_hexcrc -a 0x10000000 -c 0x10000448 -i intel.hex -o crc.hex           \n";
$synopsis    .= "#                                                                                         \n";

$description  = "#                                                                                         \n";
$description .= "#  DESCRIPTION                                                                            \n";
$description .= "#                                                                                         \n";
$description .= "#    This script calculates a CCITT-CRC value for an Intel-32 hex file, and stores the    \n";
$description .= "#    CRC in the output hex file immediately after the last data byte. When storing the    \n";
$description .= "#    CRC, it swaps the MSB and LSB so that when verifying the CRC it will always be 0.    \n";
$description .= "#    It also pads out the file, if necessary, to ensure that it ends on a 32-bit boundary \n";
$description .= "#    (this is done because the flash ECC operates on 32-bit words, which means the        \n";
$description .= "#    smallest programming size is a word).                                                \n";
$description .= "#                                                                                         \n";
$description .= "#    The script will also determine the address of the last code byte in the input file,  \n";
$description .= "#    and will write this address (plus one) to the location specified by <crc_addr>.      \n";
$description .= "#    This allows the CRC to cover only the range in which code resides, allowing the      \n";
$description .= "#    remaining space to remain in an erased state (for flash).                            \n";
$description .= "#                                                                                         \n";
$description .= "#    The script assumes that there are no unused addresses in the code space. Any         \n";
$description .= "#    unfilled addresses (between the lowest and highest addresses encountered in the      \n";
$description .= "#    input file) will be flagged as an error.                                             \n";
$description .= "#                                                                                         \n";
$description .= "#                                                                                         \n";
$description .= "#    An Intel-32 HEX file is an ASCII file with one record per line. Each line  has the   \n";
$description .= "#    following format:                                                                    \n";
$description .= "#                                                                                         \n";
$description .= "#      Byte 1     Record Marker: The first character of the line is always a colon (ASCII \n";
$description .= "#                                0x3A) to identify the line as an Intel HEX file.         \n";
$description .= "#                                                                                         \n";
$description .= "#      Byte 2-3   Record Length: This field contains the number of data bytes in the      \n";
$description .= "#                                record represented as a 2-digit hex number. This is the  \n";
$description .= "#                                total number of data bytes, not including the checksum   \n";
$description .= "#                                byte nor the first 9 characters of the line.             \n";
$description .= "#                                                                                         \n";
$description .= "#      Byte 4-7   Address:       This field contains the address where the data should be \n";
$description .= "#                                loaded into device memory. This is a value from 0 to     \n";
$description .= "#                                65,535 represented as a 4-digit hex value.               \n";
$description .= "#                                                                                         \n";
$description .= "#      Byte 8-9   Record Type:   This field indicates the type of record for this line.   \n";
$description .= "#                                The possible values are:                                 \n";
$description .= "#                                   00 == normal data                                     \n";
$description .= "#                                   01 == End of file                                     \n";
$description .= "#                                   02 == Extended Segment Address. The data value from   \n";
$description .= "#                                         this line (bytes 10-13), shifted left by 4,     \n";
$description .= "#                                         will be added to sebsequent addresses. This     \n";
$description .= "#                                         supports an address range of up to 1MB.         \n";
$description .= "#                                         This record won't be used by Galaxy, since the  \n";
$description .= "#                                         CM3 has a 4GB address range.                    \n";
$description .= "#                                   03 == Start address for certain Intel processors.     \n";
$description .= "#                                         Not used for ARM processors. This record may be \n";
$description .= "#                                         generated by tools such as ARM's fromelf, but   \n";
$description .= "#                                         will be removed by this script.                 \n";
$description .= "#                                   04 == Linear Base Address. The data value from this   \n";
$description .= "#                                         line (bytes 10-13), shifted left by 16, will    \n";
$description .= "#                                         be added to sebsequent addresses. This supports \n";
$description .= "#                                         an address range of up to 4GB.                  \n";
$description .= "#                                   05 == Start address for certain Intel processors.     \n";
$description .= "#                                         Not used for ARM processors. This record may be \n";
$description .= "#                                         generated by tools such as ARM's fromelf, but   \n";
$description .= "#                                         will be removed by this script.                 \n";
$description .= "#                                                                                         \n";
$description .= "#      Byte 10-?? Data Bytes:    The following bytes are the actual data that will be     \n";
$description .= "#                                programmed into device memory. The data is represented   \n";
$description .= "#                                as 2-digit hex values.                                   \n";
$description .= "#                                                                                         \n";
$description .= "#                 Checksum:      The last two characters of the line are a checksum for   \n";
$description .= "#                                the line. The checksum value is calculated by taking     \n";
$description .= "#                                the two's compliment of the sum of all the preceeding    \n";
$description .= "#                                data bytes, excluding the checksum byte itself and the   \n";
$description .= "#                                colon at the beginning of the line.                      \n";
$description .= "#                                                                                         \n";
$description .= "#    These are examples of normal data records, the first with 16 bytes of data starting  \n";
$description .= "#    at address 0x0030, and the second with 32 bytes of data starting at 0x02A0 (note     \n";
$description .= "#    that the addresses are relative to the most recent Linear Base Address):             \n";
$description .= "#                                                                                         \n";
$description .= "#       :10003000119B03104B9B0310859B0310BF9B031058                                       \n";
$description .= "#       :2002A0007ED32D01010101017ED32D01010101017ED32D01010101017ED32D010101010132       \n";
$description .= "#                                                                                         \n";
$description .= "#    This is an example of an End-of-File record (note that the address field for this    \n";
$description .= "#    record is always 0000):                                                              \n";
$description .= "#                                                                                         \n";
$description .= "#       :00000001FF                                                                       \n";
$description .= "#                                                                                         \n";
$description .= "#    This is an example of a Linear Base Address record, specifying that 0x10000000       \n";
$description .= "#    should be added to subsequent addresses (note that the address field for this record \n";
$description .= "#    is always 0000):                                                                     \n";
$description .= "#                                                                                         \n";
$description .= "#       :020000041000EA                                                                   \n";
$description .= "#                                                                                         \n";
$description .= "###########################################################################################\n";


#--------------------------------------------------------------------------------------
#
# Initialize constants.
#
#--------------------------------------------------------------------------------------

# Default values for arguments
my $start_address       = 0x10000000;            # -a
my $crc_address         = 0x10000448;            # -c
my $build_rev_name      = "";                    # -b
my $infile_name         = "code.intel.hex";      # -i
my $logfile_name        = "";                    # -l
my $outfile_name        = "code_crc.intel.hex";  # -o
my $rtl_outfile_name    = "code_rtl.hex";        # -r
my $specified_code_size = 0;                     # -s
my $verbose             = 0;                     # -v

# Other constants
my $script_name         = "galaxy_hexcrc";
my $help                = $purpose . $synopsis . $description;
my $data_length         = 16;  # 16 data bytes per hex file line

# Global data;
my @memory_array;
my $min_address_line_count;
my $max_address_line_count;

#--------------------------------------------------------------------------------------
#
# Process command line arguments
#
#--------------------------------------------------------------------------------------
while ($ARGV[0] =~ /^-/)
{
   $_ = shift;
   if (/^-h/)
   {
      $help =~ s/#//g;
      print $help;
      exit;
   }
   elsif (/^-a/)
   {
      $start_address = hex(shift);
   }
   elsif (/^-c/)
   {
      $crc_address = hex(shift);
   }
   elsif (/^-b/)
   {
      $build_rev_name = shift;
   }
   elsif (/^-i/)
   {
      $infile_name = shift;
   }
   elsif (/^-l/)
   {
      $logfile_name = shift;
   }
   elsif (/^-o/)
   {
      $outfile_name = shift;
   }
   elsif (/^-r/)
   {
      $rtl_outfile_name = shift;
   }
   elsif (/^-s/)
   {
      $specified_code_size = hex(shift);
   }
   elsif (/^-v/)
   {
      $verbose = 1;
   }
   else
   {
      print "Unrecognized switch: $_\n";
      $synopsis =~ s/#//g;
      print $synopsis;
      exit;
   }
}

# Check for extraneous arguments
if (defined($ARGV[0]))
{
   print "Invalid argument(s): @ARGV\n";
   $synopsis =~ s/#//g;
   print $synopsis;
   exit;
}


#--------------------------------------------------------------------------------------
# 
# Initialize input and output files.
#
#--------------------------------------------------------------------------------------

open(INFILE,     "<$infile_name")      or die "Can't open $infile_name: $!\n";
open(OUTFILE,    ">$outfile_name")     or die "Can't open $outfile_name: $!\n";
if ($logfile_name) {
    open(LOGFILE, ">$logfile_name") or die "Can't open $logfile_name: $!\n";
}

# Output header info to log file
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $month = ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December")[$mon];
my $timestamp = sprintf "$month %02d, %04d  %02d:%02d", $mday, $year+1900, $hour, $min;

my $svn_revision_string = '\$Revision: 71 $';  # Get file revision via SVN keyword
my $revision;
if ($svn_revision_string =~ /Revision:\s+(\d+)/)  
{
   $revision = "Revision $1";
}

Log(sprintf "$script_name:  $revision\n");
Log(sprintf "Run Time:  $timestamp\n");
Log(sprintf "\n");
Log(sprintf "Input file:              $infile_name\n");
Log(sprintf "Output file:             $outfile_name\n");
Log(sprintf "Specified code size:     0x%05X\n", $specified_code_size);
Log(sprintf "Location of CRC address: 0x%08X\n", $crc_address);
Log(sprintf "\n");
   

#--------------------------------------------------------------------------------------
# 
# Process the input file. The data from the file will be placed in an array, indexed
# by address.
#
# This script's basic approach is to populate an array (memory_array) with all of the 
# data from the input file, indexed by address; modify the array to add CRC info; and
# then write the array to the output file, adding the necessary formatting info. However,
# there's a limit to how large an array can be accommodated. So, when we're dealing with
# an address range of 0x10000000-0x1007FFFF as in the Galaxy flash (or 0x01000000-
# 0x0101FFFF for ROM), we have to subtract the start address from the memory_array index
# to get the index (and thus the array size) into an acceptable range. That's why you'll
# see, for example, "memory_array[$index - $start_address]" whenever memory_array is
# accessed.
#
#--------------------------------------------------------------------------------------
my $line_count   = 0;
my $base_address = 0;
my $min_address  = 0xFFFFFFFF;
my $max_address  = 0;

while ( my $input_line = <INFILE> )
{
   if ($input_line =~ /^\:(\w\w)(\w\w\w\w)(\w\w)(\w*)(\w\w)$/)
   {
      my $length   = hex($1);
      my $address  = hex($2) + $base_address;
      my $type     = $3;
      my $data     = $4;
      my $checksum = hex($5);

      # Check for invalid checksum for the record
      my $calculated_checksum = &Checksum(substr($input_line, 0, length($input_line)-3));
      if ($checksum != $calculated_checksum)
      {
         Log(sprintf "ERROR: invalid checksum encountered (Line $line_count)\n");
         Log(sprintf "       $input_line");
         Log(sprintf "       Expected: %02X     Calculated: %02X\n", $checksum, $calculated_checksum);
         exit();
      }

      # Check for invalid record type
      if (($type < 0) || ($type > 5))
      {
         Log(sprintf "ERROR: out-of-range record type (%d) encountered (Line $line_count, starting address 0x%05X)\n", $type, $address);
         exit();
      }

      # Normal data line
      if ($type == 0)
      {
         # Check for out-of-range length field
         if ($length > $data_length)
         {
            Log(sprintf "ERROR: invalid record length ($length) encountered - should be <= $data_length (Line $line_count, starting address 0x%05X)\n", $address);
            exit();
         }

         for (my $i=0; $i<$length; $i++)
         {
            $memory_array[$address - $start_address] = substr($data, $i*2, 2);

            # Determine min and max addresses.
            if ($address < $min_address)
            {
               $min_address = $address;
               $min_address_line_count = $line_count;
            }
            if (($address + $data_length - 1) > $max_address)
            {
               $max_address = $address;
               $max_address_line_count = $line_count;
            }

            $address++;
         }
      }

      # Extended Segment Address line
      elsif ($type == 2)
      {
         Log(sprintf "WARNING: Extended Segment Address record ignored (Line $line_count)\n");
      }

      # Start Segment Address line
      elsif ($type == 3)
      {
         Log(sprintf "WARNING: Start Segment Address record ignored (Line $line_count)\n");
      }

      # Extended Linear Address line
      elsif ($type == 4)
      {
         # Check for out-of-range length field
         if ($length != 2)
         {
            Log(sprintf "ERROR: invalid Extended Segment Address record length (%d) encountered - should be 2 (Line $line_count, starting address 0x%05X)\n", $length, $address);
            exit();
         }

         $base_address = hex($data) * 0x10000;
      }

      # Start Linear Address line
      elsif ($type == 5)
      {
         Log(sprintf "WARNING: Start Linear Address record ignored (Line $line_count)\n");
      }
   }
   $line_count++;
}

# If the specified_code_size argument is non-zero, check for observed code size that doesn't match the specified code size.
if ($specified_code_size > 0) {
   my $code_size = $max_address - $min_address + 1;
   if ($code_size != $specified_code_size)
   {
       Log(sprintf "ERROR: observed code size (0x%05) doesn't match specified code size (0x%05X)\n", $code_size, $specified_code_size);
       exit();
   }
}

# Log the min and max addresses
Log(sprintf "\n");
Log(sprintf "Min address: 0x%08X (found at line $min_address_line_count)\n", $min_address);
Log(sprintf "Max address: 0x%08X (found at line $max_address_line_count)\n", $max_address);

# Store the max address (plus one) at the specified location. The CRC will be written to this address.
$memory_array[$crc_address + 3 - $start_address] = sprintf "%02X", ($max_address + 1) / 0x1000000;  #MSB
$memory_array[$crc_address + 2 - $start_address] = sprintf "%02X", ($max_address + 1) / 0x10000 % 0x100;
$memory_array[$crc_address + 1 - $start_address] = sprintf "%02X", ($max_address + 1) / 0x100 % 0x100;
$memory_array[$crc_address     - $start_address] = sprintf "%02X", ($max_address + 1) % 0x100;  #LSB

# Place the revision info.
my $MajorRev = 0;
my $MinorRev = 0;
if (open (REV_FILE, "<$build_rev_name"))
{
# TBD check values for validity?
    $MajorRev = <REV_FILE>;
    $MajorRev =~ s/.+?(\d+).*/\1/;
    $MinorRev = <REV_FILE>;
    $MinorRev =~ s/.+?(\d+).*/\1/;
    $memory_array[$crc_address - $start_address - 8] = sprintf "%02X", $MajorRev;
    $memory_array[$crc_address - $start_address - 7] = sprintf "%02X", $MinorRev;
    close REV_FILE;
}
else
{
    print "$build_rev_name file was not found!\n";
}

#--------------------------------------------------------------------------------------
# 
# Calculate the CRC and store it in the memory array immediately after the last code
# byte. The CRC needs is stored LSB followed by MSB, so when a CRC check of the entire
# image is performed, the CRC is 0.
#
#--------------------------------------------------------------------------------------

my $calculated_crc = 0xFFFF;
for (my $index=$min_address; $index <= $max_address; $index++)
{
   my $tmp = $calculated_crc ^ (hex($memory_array[$index - $start_address]) & 0xFF);

   $calculated_crc = (((($tmp<<12) & 0xf000) | (($tmp>>4) & 0x000f)) ^
             (((($tmp<< 8) & 0xff00) | (($tmp>>8) & 0x00ff)) ^
             ((( $tmp<< 7) & 0x0780) ^
             ((( $tmp    ) & 0x000f) ^ (($tmp<<3) & 0x07f8)))));
}
Log(sprintf "Calculated CRC is %04X\n", $calculated_crc);

# Store the calculated CRC at the end of the FW image. Increase max_address to account for CRC.
$max_address++;
$memory_array[$max_address - $start_address] = sprintf "%02X", $calculated_crc % 256;  # LSB
$max_address++;
$memory_array[$max_address - $start_address] = sprintf "%02X", $calculated_crc / 256;  # MSB

# Pad the FW image out, if necessary, so it ends on a word boundary. The pad bytes won't
# be included in the CRC.
my $pad_count = ($max_address + 1) % 4;
for (my $i=0; $i < $pad_count; $i++) {
   $max_address++;
   $memory_array[$max_address - $start_address] = sprintf "%02X", 0;
}


#--------------------------------------------------------------------------------------
# 
# Generate the output file.
#
#--------------------------------------------------------------------------------------

# Intel hex file records have an address range of 64K. If the starting address is >64K,
# a Extended Linear Address record must be inserted at the beginning of the output
# file. (Note that if the starting address is a multiple of 0x10000, then the extended
# linear address record will be inserted by the FOR loop below, so it is not inserted
# here.)
if (($min_address > 0x10000) && ($min_address % 0x10000 != 0)) {
   my $record = sprintf ":02000004%X000", $min_address / 0x10000;
   printf OUTFILE "$record%02X\n", &Checksum($record);
}

# Step through the memory array to generate the output file, one line at a time.
my $output_length = 1e6;# Default this to big in case it isn't set.
for (my $address=$min_address; $address<=$max_address; $address+=$output_length)
{
   my $record;
   # Intel hex file records have an address range of 64K. If the address crosses a 64K
   # boundary, an Extended Linear Address record must be inserted in the output file.
   # Since we're outputting the records in ascending address order, we can simply check
   # for address mod 64K to detect the boundary crossings.
   if ($address % 0x10000 == 0) {
      $record = sprintf ":02000004%04X", $address / 0x10000;
      printf OUTFILE "$record%02X\n", &Checksum($record);
   }

   # Insert a record "header" (colon, byte count, address, record type) when starting
   # a new line. The record type is 00 for a Data record.
   if ($address % $data_length == 0) {
      if (($max_address - $address) >= $data_length) {
         $output_length = $data_length;
      }
      else {  # Handle a partial line at the end of the file
         $output_length = $max_address - $address + 1; 
      }
      $record = sprintf ":%02X%04X00", $output_length, $address % 0x10000;  
   }

   # Output the data bytes for one line. Since this script is intended to be run with
   # an input file in which the entire memory range is filled, any undefined memory 
   # value that is encountered will result in an error message and termination of the
   # script.
   for (my $offset=0; $offset<$output_length; $offset++) {
      if (!defined($memory_array[$address+$offset-$start_address]))
      {
         Log(sprintf "ERROR: undefined value at address %08X\n", $address+$offset);
         exit();
      }
      else
      {
         $record .= $memory_array[$address+$offset-$start_address];  # Append a byte to the record.
      }
   }

   # Ouput the record, appending the checksum at the end of the record.
   printf OUTFILE "$record%02X\n", &Checksum($record);  
}

#Output the EOF record
printf OUTFILE ":00000001FF\n";

sub CalculateECC32($)
{
    my $word = shift;

    # Get the bits from the value.
    my @b;
    for (my $bit = 0; $bit < 32; $bit++)
    {
        $b[$bit] = ($word >> $bit) & 1;
    }

    my @p;
    $p[0] = $b[0]  ^ $b[1]  ^ $b[3]  ^ $b[4]  ^ $b[6]  ^ $b[8]  ^ $b[9]  ^ $b[11] ^ $b[12] ^ $b[14] ^ $b[16] ^ $b[17] ^ $b[19] ^ $b[20] ^ $b[22] ^ $b[24] ^ $b[25] ^ $b[27] ^ $b[28] ^ $b[30]; 
    $p[1] = $b[0]  ^ $b[2]  ^ $b[3]  ^ $b[5]  ^ $b[6]  ^ $b[8]  ^ $b[10] ^ $b[11] ^ $b[13] ^ $b[14] ^ $b[16] ^ $b[18] ^ $b[19] ^ $b[21] ^ $b[22] ^ $b[24] ^ $b[26] ^ $b[27] ^ $b[29] ^ $b[30];
    $p[2] = $b[1]  ^ $b[2]  ^ $b[3]  ^ $b[7]  ^ $b[9]  ^ $b[10] ^ $b[11] ^ $b[15] ^ $b[17] ^ $b[18] ^ $b[19] ^ $b[23] ^ $b[25] ^ $b[26] ^ $b[27] ^ $b[31];
    $p[3] = $b[4]  ^ $b[5]  ^ $b[6]  ^ $b[7]  ^ $b[12] ^ $b[13] ^ $b[14] ^ $b[15] ^ $b[20] ^ $b[21] ^ $b[22] ^ $b[23] ^ $b[28] ^ $b[29] ^ $b[30] ^ $b[31];
    $p[4] = $b[8]  ^ $b[9]  ^ $b[10] ^ $b[11] ^ $b[12] ^ $b[13] ^ $b[14] ^ $b[15] ^ $b[24] ^ $b[25] ^ $b[26] ^ $b[27] ^ $b[28] ^ $b[29] ^ $b[30] ^ $b[31];
    $p[5] = $b[16] ^ $b[17] ^ $b[18] ^ $b[19] ^ $b[20] ^ $b[21] ^ $b[22] ^ $b[23] ^ $b[24] ^ $b[25] ^ $b[26] ^ $b[27] ^ $b[28] ^ $b[29] ^ $b[30] ^ $b[31];
    $p[6] = $b[0]  ^ $b[1]  ^ $b[2]  ^ $b[3]  ^ $b[4]  ^ $b[5]  ^ $b[6]  ^ $b[7]  ^ $b[8]  ^ $b[9]  ^ $b[10] ^ $b[11] ^ $b[12] ^ $b[13] ^ $b[14] ^ $b[15] ^ $b[16] ^ $b[17] ^ $b[18] ^ $b[19] ^ $b[20] ^ $b[21] ^ $b[22] ^ $b[23] ^ $b[24] ^ $b[25] ^ $b[26] ^ $b[27] ^ $b[28] ^ $b[29] ^ $b[30] ^ $b[31] ^ $p[0] ^ $p[1] ^ $p[2] ^ $p[3] ^ $p[4] ^ $p[5];

    my $ECC = ($p[6] << 6)
            | ($p[5] << 5)
            | ($p[4] << 4)
            | ($p[3] << 3)
            | ($p[2] << 2)
            | ($p[1] << 1)
            |  $p[0];

    $ECC = ~$ECC & 0x7F;

    return $ECC;
}

# Step through the memory array to generate the output file, one line at a time.
# $start_address -> Start location of this memory.
# $min_address   -> Smallest address with valid data.
# $max_address   -> Largest address with valid data.
# This code produces the memory image from the memory start to the image finish.
# Note that $memory_array is padded above so it's a multiple of 4 bytes.
open(RTLOUTFILE, ">$rtl_outfile_name") or die "Can't open $rtl_outfile_name: $!\n";
# True if we're building a flash image.
my $flash_image = (0x10000000 == $start_address);
if ($flash_image)
{
    my $ecc_file_name = $rtl_outfile_name;
    $ecc_file_name =~ s/\.hex/_ecc.hex/i;
    open(RTLECCOUTFILE, ">$ecc_file_name") or die "Can't open $ecc_file_name: $!\n";
}
for (my $index = 0; $index < $max_address - $start_address; $index += 4)
{
    my $wordstr = "$memory_array[$index+3]$memory_array[$index+2]$memory_array[$index+1]$memory_array[$index]";
    printf RTLOUTFILE "$wordstr\n";
    if ($flash_image)
    {
        my $ECCByte = CalculateECC32(hex $wordstr);
        printf RTLECCOUTFILE "%02X\n", $ECCByte;
    }
}
# We want to generate ECC values for the entire flash (not just the image) so continue to the end.
if ($flash_image)
{
    for (my $index = $max_address - $start_address; $index < 0x7FFFF; $index += 4)
    {
        # ECC for 00000000 is 7Fh.
        printf RTLECCOUTFILE "7F\n";
    }
}
close(RTLOUTFILE);
close(RTLECCOUTFILE);


#--------------------------------------------------------------------------------------
# 
# Close the input and output files.
#
#--------------------------------------------------------------------------------------
close(INFILE);
close(OUTFILE);
close(LOGFILE);


#****************************************************************************************
#
# Unit Name:    Checksum
#
# Parameters:   record
#
# Globals:      None
#
# Return Value: None
#
# Description:  Given a string of hex bytes that represent an Intel hex file record,
#               return the checksum as calculated per the Intel hex file standard.
#
#*****************************************************************************************
sub Checksum
{
   my ($record) = @_;
   my $index = 1;  # Skip the initial colon
   my $checksum = 0;
   my $record_length;

   $record_length = length($record);
   while ($index < $record_length)
   {
      $checksum += hex(substr($record, $index, 2));
      $index += 2;
   }

   return ((0x100 - ($checksum % 256)) % 256);
}


#****************************************************************************************
#
# Unit Name:    Log
#
# Parameters:   message
#
# Globals:      None
#
# Return Value: None
#
# Description:  Prints the given message to the log file (if the logfile argument was
#               specified and LOGFILE was successfully opened) and/or the screen (if the
#               verbose argument was specfied).
#
#*****************************************************************************************
sub Log
{
   my ($message) = @_;

   printf LOGFILE $message;

   if ($verbose) {
      printf $message;
   }
}
